home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Plus 2004 #11
/
Amiga Plus CD - 2004 - No. 11.iso
/
AmiSoft
/
Comm
/
mail
/
SpamFryer.lha
/
SpamFryer
/
SpamFryer.rexx
< prev
next >
Wrap
OS/2 REXX Batch file
|
2004-08-20
|
21KB
|
655 lines
/* sys:rexxc/rx
*
* Checks through emails on a POP3 host and purges the unwanted ones.
*
* Shows a summary of waiting messages and deletes any with html right
* the start, or subjects or a recipient address known to be bogus.
* Includes special sifting for recent Windoze .PIF worms and mails
* flagged by SpamAssasin on the server. Easy to extend for similar
* cases, but catches 80..90 per cent of spams without special code.
*
* SpamFryer by <amiga (at) studio (dot) co (dot) uk>
* Improved by <allan (dot) tnt (at) post (dot) tele (dot) dk>
* Inspired by ChkMail (on Aminet) written in 1994 by
* Matt White <whitem (at) duke (dot) usask (dot) ca>
*
* $VER: SpamFryer.rexx 3:9 (2004-8-19) by Simon N Goodwin $
*
* SpamFryer.text lists changes since November 2003 Aminet release.
*/
Account. = '' /* resets the array */
count.1 = 0
count.2 = 0
/*** CONFIGURATION BLOCK **/
/* Account details can be in the file SpamFryer.accounts, or here in the following format: */
/* Account.1.1 = "new.mail.org" */ /* IP number e.g. 194.5.6.7 or domain e.g. mail.isp.net */
/* Account.1.2 = "username1" */ /* Your user name - typically your ISP account name */
/* Account.1.3 = "password1" */ /* The login password you use for email - confidential! */
/* Continue for Account.2.1, Account 2.2 and Account.2.3 etc if you have several accounts. */
keepList = 'SpamFryer.keepList' /* The path and name of your file of details of mails you want to keep */
loseList = 'SpamFryer.loseList' /* The path and name of your file of details of mails you want to lose */
accountFile = 'SpamFryer.accounts' /* The file of login details for your POP3 accounts (if not set above) */
logFile = 'T:SpamFryer.log' /* Specifies the log file this program generates */
screenName = '*' /* log window public screen name, '' for no window or '*' for the default */
lineMax = 60 /* The maximum width of subject line characters reported to console */
noDigits = 0 /* Non-zero to lose all mails with digits in their To: address */
/**************************** END OF CONFIGURATION BLOCK ****************************/
OPTIONS results /* make Arexx return results from various functions */
PARSE ARG quiet .
IF (UPPER(quiet) == "VERBOSE") THEN
verbose = 1
ELSE
verbose = 0
IF screenName ~='' THEN DO
CALL close STDOUT
CALL open(STDOUT, "con:48/48/590/150/SpamFryer3 Log -- Click CLOSE gadget when finished/CLOSE/WAIT/SCREEN " screenName, w)
END
IF logFile ~='' THEN
IF ~open(log,logFile,'A') THEN
IF ~open(log,logFile,'W') THEN
logFile = ''
CALL Check_Accounts
keep = 1 /* Symbolic category names for readability */
lose = 2
gTo = 1
gFrom = 2
gReply = 3
gSubject= 4
gEarly = 5
gDating = 6 /* Used for combination test, not in list */
/* load the lose and keep lists */
list.keep = keepList
list.lose = loseList
listfield. = 0
listpattern. = ''
/****************************************************************/
/* Embed rules here, or put them in keeplist and loselist files */
/* Let through mails specifically about this program */
CALL keep_rule "Subject: SpamFryer"
CALL keep_rule "Subject: Spam Frier" /* Allow various spellings */
CALL keep_rule "Subject: Spam Fryer"
/* These three rules catch a lot of spam */
CALL lose_rule "Subject: *SPAM*" /* Premarked by mail server */
CALL lose_rule "To: www." /* Idiot mailing web server */
CALL lose_rule "To: undisclosed-recipients" /* A spam indicator */
/* These all signify a HTML mail with no plain text - i.e. spam */
CALL lose_rule "Early-Ref: </"
CALL lose_rule "Early-Ref: <HTML>"
CALL lose_rule "Early-Ref: text/html"
/****************************************************************/
DO type = keep TO lose
IF list.type ~='' THEN DO
IF ~OPEN(list,list.type,'R') THEN DO
sigh = list.type 'not found.'
SAY sigh
IF logFile ~= '' THEN CALL WRITELN(log, sigh)
END
ELSE DO
IF verbose THEN SAY list.type "rules:"
iterations = 0
counter = count.type
DO WHILE ~EOF(list)
iterations = iterations + 1
listerror = 0
CALL learn_rule READLN(list), type
IF listerror THEN DO
sigh = 'ERROR in' list.type 'line' iterations 'skipped.'
SAY sigh
IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh)
END
END
CALL CLOSE(list)
IF verbose THEN DO
IF counter = 0 THEN moan = list.type 'not loaded.'
ELSE moan = list.type 'loaded.'
SAY moan
IF logFile ~='' THEN CALL WRITELN(log, moan)
END
END
END
END
IF Account.1.1 = '' THEN DO
sigh = 'You need to configure your mail host and user name into SpamFryer.rexx'
SAY sigh
IF logFile ~='' THEN CALL writeln(log, sigh)
IF OPEN(list,accountFile,'R') THEN DO
sigh = '(or' accountFile 'if you prefer to keep these in a separate file).'
SAY sigh
IF logFile ~='' THEN CALL writeln(log, sigh)
CALL CLOSE(list)
END
IF logFile ~='' THEN CALL CLOSE(log)
EXIT 5
END
idx = 1
DO WHILE LENGTH(Account.idx.1) > 0
pophost = Account.idx.1
username = Account.idx.2
password = Account.idx.3
IF password = '' THEN DO /* Interactive password support by John 'Kwah' Smith */
CALL addlib('rexxreqtools.library', 0, -30, 37) /* Implicitly uses reqtools.library */
CALL addlib(LibName, Priority, Offset, Version)
IF ~show('L', 'rexxreqtools.library') THEN DO
moan = '[rexx]reqtools library for password input not found.'
SAY moan
IF logFile ~='' THEN DO
IF verbose THEN CALL writeln(log,moan)
CALL close(log)
END
EXIT 20
END
password = rtgetstring( , ('Enter password for ' || pophost) ,,, 'rtgs_invisible = >> true',)
END
SAY ''
SAY 'Checking login ' || username || '.'
netsend = 'TCP:'||pophost||'/pop3'
CALL open(net,netsend,'A')
IF result = 0 THEN DO
moan = 'Login' username || ': -ERR not found.'
SAY moan
IF logFile ~='' THEN DO
IF verbose THEN CALL writeln(log,moan)
CALL close(log)
END
EXIT 10
END
instring=READLN(net)
CALL test_str(instring)
CALL writeln(net,'USER '||username||'0d'x)
instring=READLN(net)
CALL test_str(instring)
CALL writeln(net,'PASS '||password||'0d'x)
instring=READLN(net)
CALL test_str(instring)
CALL writeln(net,'STAT'||'0d'x)
instring=READLN(net)
CALL test_str(instring)
info = substr(instring,5,length(instring)-5)
parse var info nmess ' ' noct
IF nmess = 0 THEN DO
SAY 'There are no messages waiting.'
END
ELSE DO
moan = 'Messages:' nmess ' Bytes:' noct
SAY moan
IF logFile ~= '' & verbose THEN CALL writeln(log, moan)
/* Clear counters */
nlose = 0
IF logFile ~='' THEN DO /* Per-account headings requested by Elwood */
CALL writeln(log,'')
CALL writech(log,'Account ' || username || ' on ' pophost ' had ' || nmess)
IF nmess = 1 THEN CALL writeln(log,'message.')
ELSE CALL writeln(log,' messages.')
END
/* Process all messages */
DO message = 1 TO nmess
CALL writeln(net,'TOP '||message||' 12'||'0d'x)
instring = ''
msgdate = ''
msgfrom = ''
msgsubj = ''
msgrepl = ''
bad = 0
good = 0
dating = 0
datematch = 0
digitsift = 0
field = 0
DO UNTIL (LEFT(instring,2)='.' || '0d'x)
instring=READLN(net)
IF good & msgdate ~='' & msgfrom ~='' & msgsubj ~='' THEN ITERATE
/* Speed up if we know it's not spam */
/* Speed up if no keeplist: IF bad & ~verbose & msgsubj ~='' THEN ITERATE */
inCaps = UPPER(instring)
IF LEFT(instring,6)='From: ' THEN DO
field = gFrom
msgfrom = instring
DO i = 1 TO listfield.keep.gFrom
IF POS(listpattern.keep.gFrom.i,inCaps)>0 THEN CALL good_one
END
DO i = 1 TO listfield.lose.gFrom
IF POS(listpattern.lose.gFrom.i,inCaps)>0 THEN CALL bad_one
END
END
IF LEFT(instring,4)='To: ' THEN DO
IF noDigits THEN DO
DO i = 0 to 9 /* Reject mails with any digits in recipient address */
IF POS(i,instring)>0 THEN DO
bad = 1
digitsift = digitsift + 1
IF verbose THEN DO
SAY 'Lost' message 'for digit in address'
END
END
END
END
field = gTo
DO i = 1 TO listfield.keep.gTo
IF POS(listpattern.keep.gTo.i,inCaps)>0 THEN CALL good_one
END
DO i = 1 TO listfield.lose.gTo
IF POS(listpattern.lose.gTo.i,inCaps)>0 THEN CALL bad_one
END
END
IF LEFT(instring,10)='Reply-To: ' THEN DO
field = gReply
msgrepl = instring
DO i = 1 TO listfield.keep.gReply
IF POS(listpattern.keep.gReply.i,inCaps)>0 THEN CALL good_one
END
DO i = 1 TO listfield.lose.gReply
IF POS(listpattern.lose.gReply.i,inCaps)>0 THEN CALL bad_one
END
END
IF LEFT(instring,9)='Subject: ' THEN DO
field = gSubject
msgsubj = instring
DO i = 1 TO listfield.keep.gSubject
IF POS(listpattern.keep.gSubject.i,inCaps)>0 THEN CALL good_one
END
DO i = 1 TO listfield.lose.gSubject
IF POS(listpattern.lose.gSubject.i,inCaps)>0 THEN CALL bad_one
END
END
IF LEFT(instring,6)='Date: ' THEN msgdate = right(instring,length(instring)-6)
/* Several of the following indicate notorious dating sites */
IF POS(' DATING ',inCaps)>0 THEN dating = dating + 1;
IF POS('CREATED BY WOMEN',inCaps)>0 THEN dating = dating + 1;
field = gEarly
DO i = 1 TO listfield.keep.gEarly
IF POS(listpattern.keep.gEarly.i,inCaps)>0 THEN CALL good_one
END
DO i = 1 TO listfield.lose.gEarly
IF POS(listpattern.lose.gEarly.i,inCaps)>0 THEN CALL bad_one
END
END
IF dating>1 THEN DO
field = gDating /* Require two references to zap a mail */
CALL bad_one
END
IF msgfrom = '' THEN msgfrom = msgrepl
IF (LENGTH(msgfrom) + LENGTH(msgsubj)) = 0 THEN bad = 1 /* © Elwood */
IF msgfrom = '' THEN msgfrom = 'From: ???????????'
IF msgdate = '' THEN msgdate = 'Date: ???????????'
IF msgsubj = '' THEN msgsubj = 'No subject'
IF length(msgsubj) > lineMax THEN msgsubj = LEFT(msgsubj,lineMax)
IF verbose THEN DO
SAY ''
SAY 'Message '||message||' of' nmess ': '||msgdate
SAY msgfrom
SAY msgsubj
END
IF (good=0) & (bad>0) THEN DO
IF ~verbose THEN SAY "Fry" message "of" nmess msgsubj
IF logFile ~= '' THEN DO
CALL writeln(log,'Message '||message||': '||msgdate)
CALL writeln(log,msgfrom)
CALL writeln(log,msgsubj)
END
IF bad THEN DO
CALL writeln(net,'DELE '||message||'0d'x)
IF verbose THEN DO
moan = "$$$$$ Fried"
SAY moan
IF logFile ~= '' THEN CALL writeln(log,moan)
END
instring=READLN(net)
nlose = nlose + 1
CALL test_str(instring)
ITERATE message
END
END
IF ~verbose THEN ITERATE message
moan = "----- Left on server"
SAY moan
IF logFile ~='' & verbose THEN DO
CALL writeln(log,'Message '||message||': '||msgdate)
CALL writeln(log,msgfrom)
CALL writeln(log,msgsubj)
CALL writeln(log,moan)
END
END /* Loop for all messages */
IF nmess ~= 0 THEN DO
SAY ''
other = nmess-nlose
moan = nlose "of" nmess "mail"
IF nmess ~=1 THEN moan = moan || "s"
moan = "Fried" moan "for account" username "on" pophost
SAY moan
IF logFile ~='' & verbose THEN CALL writeln(log,moan)
END
END /* Some messages */
CALL writeln(net,'QUIT'||'0d'x)
CALL close(net)
idx = idx + 1
END /* do while any accounts unchecked */
IF logFile ~='' THEN CALL close(log)
EXIT 0
/* good_one keeps track of emails identified by the keeplist */
good_one:
IF field = 0 THEN RETURN 0 /* Unlikely error */
good = 1
IF verbose THEN DO
SAY 'Kept' messsage 'for' listpattern.keep.field.i
listpattern.keep.field.i.1 = listpattern.keep.field.i.1 + 1
END
return 1
/* bad_one keeps track of emails identified by the loselist */
bad_one:
IF field = 0 THEN RETURN 0 /* Error */
bad = 1
IF verbose THEN DO
IF field = gDating THEN DO
SAY 'Lost' message 'for dating reference'
datematch = datematch + 1
END
ELSE DO
SAY 'Lost' message 'for' listpattern.lose.field.i
listpattern.lose.field.i.1 = listpattern.lose.field.i.1 + 1
END
END
return 1
/* keep_rule teaches the program a way to identify good emails */
keep_rule:
listerror = 0
CALL learn_rule arg(1), keep
IF listerror THEN DO
sigh = 'ERROR: Rule "' arg(1) '" in SpamFryer.rexx skipped.'
SAY sigh
IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh)
END
return 1
/* lose_rule teaches the program a way to identify bad emails */
lose_rule:
listerror = 0
CALL learn_rule arg(1), lose
IF listerror THEN DO
sigh = 'ERROR: Rule "' arg(1) '" in SpamFryer.rexx skipped.'
SAY sigh
IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh)
END
return 1
/* learn_rule teaches the program a way to categorise emails. */
/* The first argument is the rule. The second is the category */
/* listerror is set if the rule is not correctly formatted. */
learn_rule:
instring = UPPER(arg(1))
kind = arg(2)
PARSE VAR instring word1 word2 . /* get the first and the second word in the line */
IF word1 = "//" THEN return 1 /* Allow C++ comments in the configuration lines */
IF word2 = '' THEN DO
IF word1 ~= '' THEN listerror = 1 /* Error if there is only one word in the string */
END
ELSE DO
field = 0
IF RIGHT(word1,1) ~= ':' THEN word1 = word1 || ':'
IF word1 = 'TO:' THEN field = gTo
IF word1 = 'FROM:' THEN field = gFrom
IF word1 = 'REPLY-TO:' THEN field = gReply
IF word1 = 'REPLYTO:' THEN field = gReply
IF word1 = 'SUBJECT:' THEN field = gSubject
IF word1 = 'EARLY-REF:' THEN field = gEarly
IF word1 = 'EARLYREF:' THEN field = gEarly
IF field = 0 THEN listerror = 1 /* Error, unexpected first word on line */
ELSE DO
instring = STRIP(instring,'L')
instring = RIGHT(instring,LENGTH(instring)-POS(' ',instring)) /* cut off field parameter, leaving only the search pattern */
instring = STRIP(instring)
IF RIGHT(instring,1) = "'" & LEFT(instring,1)="'" THEN instring = STRIP(instring,'B',"'")
IF RIGHT(instring,1) = '"' & LEFT(instring,1)='"' THEN instring = STRIP(instring,'B','"')
listfield.kind.field = listfield.kind.field + 1 /* count entries in each field parameter for each list */
counter = listfield.kind.field
listpattern.kind.field.counter = instring /* Store search pattern */
listpattern.kind.field.counter.1 = 0 /* No matches yet */
IF verbose THEN DO
IF kind = keep THEN sigh = 'KEEP'
ELSE sigh = 'LOSE'
SAY sigh counter word1 instring
END
END
END
return 1
/* check_accounts reads POP3 account login details from a file */
Check_Accounts:
IF OPEN(list,accountFile,'R') THEN DO
accounts = 1
DO WHILE Account.accounts.1 ~=''
accounts = accounts + 1 /* Skip acounts defined in this file */
END
userSet = ''
hostSet = ''
passSet = ''
counter = count.type
DO WHILE ~EOF(list)
instring = READLN(list)
PARSE VAR instring word1 word2 . /* get the first and the second word in the line */
IF word1 ~= "//" & word1 ~= '' THEN DO
word1 = UPPER(word1)
field = 0
IF RIGHT(word1,1) ~= ':' THEN word1 = word1 || ':'
IF word1 = 'USERNAME:' THEN field = 2
IF word1 = 'PASSWORD:' THEN field = 3
IF word1 = 'HOSTNAME:' THEN field = 1
IF field = 0 THEN DO
moan = 'ERROR: Unrecognised item' word1 'in' accountFile
say moan
IF logFile ~='' THEN CALL WRITELN(log, moan)
END
IF RIGHT(word2,1) = "'" & LEFT(word2,1)="'" THEN word2 = STRIP(word2,'B',"'")
IF RIGHT(word2,1) = '"' & LEFT(word2,1)='"' THEN word2 = STRIP(word2,'B','"')
/* Make sure each host has one associated username and one or zero passwords */
ELSE DO
IF field = 1 THEN DO
IF hostSet ~= '' THEN DO
IF userSet = '' THEN DO
sigh = 'ERROR: No user name configured for host' hostSet 'in' AccountFile
SAY sigh
IF logFile ~= '' THEN CALL WRITELN(log, sigh)
END
ELSE DO
accounts = accounts + 1
passSet = ''
userSet = ''
END
END
hostSet = word2
END
IF field = 2 THEN DO
IF userSet ~= '' THEN DO
IF hostSet = '' THEN DO
sigh = 'ERROR: No host configured for user' userSet 'in' AccountFile
SAY sigh
IF logFile ~= '' THEN CALL WRITELN(log, sigh)
END
ELSE DO
accounts = accounts + 1
hostSet = ''
passSet = ''
END
END
userSet = word2
END
IF field = 3 THEN DO
IF passSet ~= '' THEN DO
sigh = 'ERROR: Duplicate passwords' passSet 'and' word2 'in' AccountFile
SAY sigh
IF logFile ~= '' THEN CALL WRITELN(log, sigh)
END
passSet = word2
END
Account.accounts.field = word2
END /* Valid field */
END /* Not comment or blank line */
END /* WHILE list */
CALL CLOSE(list)
IF userSet = '' & hostSet = '' THEN DO
IF passSet ~= '' THEN DO
sigh = 'ERROR: Password' passSet 'in' AccountFile 'needs a host and user name'
SAY sigh
IF logFile ~= '' THEN CALL WRITELN(log, sigh)
END
END
ELSE DO
IF hostSet = '' THEN DO
sigh = 'ERROR: No host configured for user' userSet 'in' AccountFile
SAY sigh
IF logFile ~= '' THEN CALL WRITELN(log, sigh)
END
IF userSet = '' THEN DO
sigh = 'ERROR: No user name configured for host' hostSet 'in' AccountFile
SAY sigh
IF logFile ~= '' THEN CALL WRITELN(log, sigh)
END
END
END
IF verbose THEN DO
accounts = 1
DO WHILE Account.accounts.1 ~=''
sigh = 'Host' Account.accounts.1 'User' Account.accounts.2 'Password' Account.accounts.3
SAY sigh
IF verbose THEN CALL writeln(log, sigh)
accounts = accounts + 1
END
END
return 1
/* test_str tests the response from the POP server: +OK or -ERR */
test_str:
test = arg(1)
/* SAY 'Server Response:' test */
IF POS('+OK',test)~=0 THEN
return 1
ELSE
moan = 'Login' username || ':' test
SAY moan
IF logFile ~='' THEN DO
IF verbose THEN CALL writeln(log, moan)
CALL close(log)
END
CALL writeln(net,'QUIT'||'0d'x)
CALL close(net)
EXIT 10